package com.github.grpc.client.service;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import io.grpc.Attributes;
import io.grpc.EquivalentAddressGroup;
import io.grpc.NameResolver;
import io.grpc.internal.SharedResourceHolder;
import lombok.extern.slf4j.Slf4j;
import mousio.etcd4j.EtcdClient;
import mousio.etcd4j.responses.EtcdAuthenticationException;
import mousio.etcd4j.responses.EtcdException;
import mousio.etcd4j.responses.EtcdKeysResponse;

import javax.annotation.concurrent.GuardedBy;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;

@Slf4j
public class EtcdClientChannelNameResolver extends NameResolver {

    private final String name;

    private final EtcdClient etcdClient;

    private final Attributes attributes;

    private final SharedResourceHolder.Resource<ExecutorService> executorResource;

    @GuardedBy("this")
    private boolean shutdown;

    @GuardedBy("this")
    private ExecutorService executor;

    @GuardedBy("this")
    private boolean resolving;

    @GuardedBy("this")
    private Listener listener;

    public EtcdClientChannelNameResolver(String name, EtcdClient etcdClient, Attributes attributes, SharedResourceHolder.Resource<ExecutorService> executorResource) {
        this.name = name;
        this.etcdClient = etcdClient;
        this.attributes = attributes;
        this.executorResource = executorResource;
    }

    @Override
    public String getServiceAuthority() {
        return name;
    }

    @Override
    public final synchronized void start(Listener listener) {
        Preconditions.checkState(this.listener == null, "already started");
        this.listener = listener;
        executor = SharedResourceHolder.get(executorResource);
        this.listener = Preconditions.checkNotNull(listener, "listener");
        resolve();
    }


    @Override
    public final synchronized void refresh() {
        Preconditions.checkState(listener != null, "not started");
        resolve();
    }

    private final Runnable resolutionRunnable = new Runnable() {
        @Override
        public void run() {
            Listener savedListener;
            synchronized (EtcdClientChannelNameResolver.this) {
                if (shutdown) {
                    return;
                }
                savedListener = listener;
                resolving = true;
            }
            try {
                EtcdKeysResponse response = null;
                try {
                    response = etcdClient.getDir("/grpc/server/uq_aireye").send().get();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (EtcdException e) {
                    e.printStackTrace();
                } catch (EtcdAuthenticationException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }

                if (response.node.nodes == null || response.node.nodes.isEmpty()) {
                    log.warn("response node is empty, url is [ ]");
                    return;
                }

                List<EquivalentAddressGroup> equivalentAddressGroups = Lists.newArrayList();
                for (EtcdKeysResponse.EtcdNode node : response.node.nodes) {
                    String host =  node.getValue();
                    String path =  node.getKey();
                    log.info("Found gRPC server {} ,{}", path, host);
                    EquivalentAddressGroup addressGroup = new EquivalentAddressGroup(new InetSocketAddress(getIp(host), getPort(host)), Attributes.EMPTY);
                    equivalentAddressGroups.add(addressGroup);
                }

                savedListener.onAddresses(equivalentAddressGroups, Attributes.EMPTY);
            } finally {
                synchronized (EtcdClientChannelNameResolver.this) {
                    resolving = false;
                }
            }
        }
    };

    private String getIp(String key) {
        int lastSlash = key.lastIndexOf(":");
        return key.substring(0, lastSlash);
    }

    private Integer getPort(String key) {
        int lastSlash = key.lastIndexOf(":");
        String port = key.substring(lastSlash+1, key.length());
        return Integer.valueOf(port);
    }

    private String extractLastPath(String key) {
        int lastSlash = key.lastIndexOf("/");
        return key.substring(lastSlash + 1, key.length());
    }


    @GuardedBy("this")
    private void resolve() {
        if (resolving || shutdown) {
            return;
        }
        executor.execute(resolutionRunnable);
    }

    @Override
    public void shutdown() {
        if (shutdown) {
            return;
        }
        shutdown = true;
        if (executor != null) {
            executor = SharedResourceHolder.release(executorResource, executor);
        }
    }
}
